package cn.kgc.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.kgc.base.SystemConstant;
import cn.kgc.entity.Industry;
import cn.kgc.excpetion.GraceException;
import cn.kgc.form.IndustryAddForm;
import cn.kgc.form.IndustryUpdateForm;
import cn.kgc.grace.result.GraceJSONResult;
import cn.kgc.grace.result.ResponseStatusEnum;
import cn.kgc.interceptor.JwtInterceptor;
import cn.kgc.service.IndustryService;
import cn.kgc.utils.RedisUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * @author YC
 */
@RestController
@RequestMapping("/industry")
@Api(tags = "行业管理模块")
@Validated
@Slf4j
public class IndustryController {

    @Resource
    private IndustryService industryService;
    @Resource
    private RedisUtil redisUtil;

    @GetMapping("/admin/list")
    @ApiOperation(value = "获取一级行业分类列表")
    public GraceJSONResult getIndustryListTop() {
        // 管理员用户登录验证
        String adminId = JwtInterceptor.adminUserIds.get();
        if (adminId == null) {
            GraceException.display(ResponseStatusEnum.ADMIN_LOGIN_ERROR);
        }
        LambdaQueryWrapper<Industry> wrapper = new LambdaQueryWrapper<Industry>()
                .eq(Industry::getLevel, 1).eq(Industry::getFatherId, 0)
                .orderByAsc(Industry::getSort);
        return GraceJSONResult.ok(industryService.list(wrapper));
    }

    @GetMapping("/app/list")
    @ApiOperation("普通用户获取一级行业分类列表")
    public GraceJSONResult appIndustryListTop() {
        // 普通用户登录验证
        String userId = JwtInterceptor.userIds.get();
        if (StrUtil.isBlank(userId)) {
            GraceException.display(ResponseStatusEnum.UN_LOGIN);
        }
        // 使用redis作为缓存
        String key = SystemConstant.INDUSTRY_TOP_PREFIX;
        Object result = redisUtil.get(key);
        if (result != null) {
            return GraceJSONResult.ok(result);
        } else {
            List<Industry> topIndustry = industryService.getTopIndustry();
            redisUtil.set(key, topIndustry);
            return GraceJSONResult.ok(topIndustry);
        }

    }

    @Resource
    private RedissonClient redissonClient;

    /**
     * 【推荐】使用缓存预热+Redisson分布式锁（白名单方案）
     * 解决缓存击穿、缓存穿透、缓存雪崩问题
     * 就算缓存服务直接挂了重启也可以重新预热从数据库拿数据
     */
    @GetMapping("/app/thirdList")
    @ApiOperation("普通用户通过一级分类ID直接获取三级行业分类列表（白名单方案）")
    public GraceJSONResult appIndustryListThird(String topId) {
        // 普通用户登录验证
        String userId = JwtInterceptor.userIds.get();
        if (StrUtil.isBlank(userId)) {
            GraceException.display(ResponseStatusEnum.UN_LOGIN);
        }
        // 从Redis缓存中判断传入的Id是否在数据库中存在
        List<Industry> industryList = (List<Industry>) redisUtil.get(SystemConstant.INDUSTRY_TOP_PREFIX);
        boolean isTopIdValid = industryList.stream()
                .anyMatch(industry -> Objects.equals(industry.getId(), topId));
        if (!isTopIdValid) {
            return GraceJSONResult.ok(Collections.emptyList());
        }
        // 使用redis作为缓存
        String key = SystemConstant.INDUSTRY_THIRD_PREFIX + topId;
        Object result = redisUtil.get(key);
        if (result != null) {
            return GraceJSONResult.ok(result);
        }
        // 防止缓存穿透，使用Redisson的分布式锁
        RLock lock = redissonClient.getLock("industry:lock:" + topId);
        List<Industry> thirdIndustry = Collections.emptyList();
        try {
            // lock.lock(); 尝试获得锁，没有获得锁一直等待，获得锁才继续执行程序
            // lock.tryLock(); 尝试获得锁，获得锁返回true，没有获得锁返回false，无论是否获得锁，都继续执行
            // 如果线程5s还没有拿到锁直接返回false，如果拿到锁30s后锁自动释放
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                thirdIndustry = industryService.getThirdIndustry(topId);
                if (thirdIndustry.isEmpty()) {
                    // 防止缓存击穿，空数据（List没有数据是空集合[]，不是null）也使用缓存但设置过期时间
                    redisUtil.set(key, thirdIndustry, 20, TimeUnit.SECONDS);
                } else {
                    redisUtil.set(key, thirdIndustry);
                }
            } else {
                log.warn("请求超时,没有获得{}下的三级行业信息", topId);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 无论是否有异常都要释放当前线程持有的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return GraceJSONResult.ok(thirdIndustry);
    }

    @Resource
    private RBloomFilter<String> industryBloomFiller;

    /**
     * 使用缓存预热+布隆过滤器
     * 解决缓存击穿、缓存穿透、缓存雪崩问题
     * 就算缓存服务直接挂了重启也可以重新预热从数据库拿数据
     */
    @GetMapping("/app/thirdList2")
    @ApiOperation("普通用户通过一级分类ID直接获取三级行业分类列表（布隆过滤器方案）")
    public GraceJSONResult appIndustryListThird2(String topId) {
        // 普通用户登录验证
        String userId = JwtInterceptor.userIds.get();
        if (StrUtil.isBlank(userId)) {
            GraceException.display(ResponseStatusEnum.UN_LOGIN);
        }
        // 使用布隆过滤器判断传入的一级行业ID是否在数据库中（布隆过滤器在程序启动时从数据库拿到所有的一级分类ID）
        if (!industryBloomFiller.contains(topId)) {
            // 不存在直接返回空集合（[]）
            return GraceJSONResult.ok(Collections.emptyList());
        }
        // 如果一级行业分类ID在数据库存在（实际不存在可能会误判为存在）
        // 【之后与白名单方案一致】使用redis作为缓存
        String key = SystemConstant.INDUSTRY_THIRD_PREFIX + topId;
        Object result = redisUtil.get(key);
        if (result != null) {
            return GraceJSONResult.ok(result);
        }
        // 防止缓存击穿，使用Redisson的分布式锁
        RLock lock = redissonClient.getLock("industry:lock:" + topId);
        List<Industry> thirdIndustry;
        try {
            // lock.lock(); 尝试获得锁，没有获得锁一直等待，获得锁才继续执行程序
            // lock.tryLock(); 尝试获得锁，获得锁返回true，没有获得锁返回false，无论是否获得锁，都继续执行
            lock.lock();
            thirdIndustry = industryService.getThirdIndustry(topId);
            if (thirdIndustry.isEmpty()) {
                // 空数据（List没有数据是空集合[]，不是null）也使用缓存但设置过期时间
                redisUtil.set(key, thirdIndustry, 10, TimeUnit.SECONDS);
            } else {
                redisUtil.set(key, thirdIndustry);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 无论是否有异常都要释放锁
            lock.unlock();
        }
        return GraceJSONResult.ok(thirdIndustry);
    }

    /**
     * 不考虑缓存服务器与canal出问题的话，可以直接从缓存中拿信息
     */
    @GetMapping("/app/thirdList3")
    @ApiOperation("普通用户通过一级分类ID直接获取三级行业分类列表（直接从缓存拿数据方案）")
    public GraceJSONResult appIndustryListThird3(String topId) {
        // 普通用户登录验证
        String userId = JwtInterceptor.userIds.get();
        if (StrUtil.isBlank(userId)) {
            GraceException.display(ResponseStatusEnum.UN_LOGIN);
        }
        String key = SystemConstant.INDUSTRY_THIRD_PREFIX + topId;
        Object result = redisUtil.get(key);
        if (result != null) {
            return GraceJSONResult.ok(result);
        }
        return GraceJSONResult.ok(Collections.emptyList());
    }

    @GetMapping("/admin/childrenList")
    @ApiOperation(value = "获取一级行业分类下子行业分类列表")
    public GraceJSONResult childrenListIndustry() {
        // 管理员用户登录验证
        String adminId = JwtInterceptor.adminUserIds.get();
        if (adminId == null) {
            GraceException.display(ResponseStatusEnum.ADMIN_LOGIN_ERROR);
        }
        return GraceJSONResult.ok(industryService.getTopIndustry());
    }

    @PostMapping("/admin/add")
    @ApiOperation(value = "添加行业分类")
    public GraceJSONResult addIndustry(@RequestBody IndustryAddForm form) {
        // 管理员用户登录验证
        String adminId = JwtInterceptor.adminUserIds.get();
        if (adminId == null) {
            GraceException.display(ResponseStatusEnum.ADMIN_LOGIN_ERROR);
        }
        Industry industry = BeanUtil.copyProperties(form, Industry.class);
        setIndustry(industry);
        boolean saveResult = industryService.save(industry);
        // 【布隆过滤器方案的新增处理】如果新增的是一级行业分类，将一级行业分类ID保存在布隆过滤器中
        if (industry.getLevel() == 1 && saveResult) {
            if (!industryBloomFiller.add(industry.getId())) {
                // 如果新增失败，可以加入消息队列重新尝试，这里直接打印日志
                log.warn("新增一级行业信息时，布隆过滤器新增一级行业分类ID：{}失败", industry.getId());
            }
        }
        return GraceJSONResult.ok(saveResult);
    }

    @PostMapping("/admin/update")
    @ApiOperation(value = "修改行业分类")
    public GraceJSONResult updateIndustry(@RequestBody IndustryUpdateForm form) {
        // 管理员用户登录验证
        String adminId = JwtInterceptor.adminUserIds.get();
        if (adminId == null) {
            GraceException.display(ResponseStatusEnum.ADMIN_LOGIN_ERROR);
        }
        LambdaUpdateWrapper<Industry> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Industry::getId, form.getId())
                .set(Industry::getName, form.getName())
                .set(Industry::getSort, form.getSort());
        return GraceJSONResult.ok(industryService.update(updateWrapper));
    }

    @DeleteMapping("/delete")
    @ApiOperation(value = "删除行业分类")
    public GraceJSONResult addIndustry(String id) {
        // 管理员用户登录验证
        String adminId = JwtInterceptor.adminUserIds.get();
        if (adminId == null) {
            GraceException.display(ResponseStatusEnum.ADMIN_LOGIN_ERROR);
        }
        // 当前行业分类下有子分类，不能删除
        if (industryService.count(new LambdaQueryWrapper<Industry>().eq(Industry::getFatherId, id)) > 0) {
            GraceException.display(ResponseStatusEnum.INDUSTRY_HAVE_CHILDREN);
        }
        return GraceJSONResult.ok(industryService.removeById(id));
    }

    private void setIndustry(Industry industry) {
        // 父级行业分类ID为空抛出异常
        String fatherId = industry.getFatherId();
        if (StrUtil.isBlank(fatherId)) {
            GraceException.display(ResponseStatusEnum.INDUSTRY_FATHER_NULL);
        }
        // 父级行业ID为0，则需要创建顶级行业分类
        if ("0".equals(fatherId)) {
            industry.setLevel(1);
            industry.setIsLeaf((short) 0);
            return;
        }
        // 根据父级行业分类ID查询父级行业分类为空，抛出异常
        Industry fatherIndustry = industryService.getById(fatherId);
        if (fatherIndustry == null) {
            GraceException.display(ResponseStatusEnum.INDUSTRY_FATHER_NULL);
        }

        if (fatherIndustry.getLevel() == 1) {
            // 父级行业是顶级行业分类，新增的行业分类应该是二级分类
            industry.setLevel(2);
            industry.setIsLeaf((short) 0);
        } else if (fatherIndustry.getLevel() == 2) {
            // 父级行业是二级分类，新增的行业分类应该是三级分类
            industry.setLevel(3);
            industry.setIsLeaf((short) 1);
        } else {
            // 父级行业是三级分类，抛出异常（数据库设计行业分类只有三层）
            GraceException.display(ResponseStatusEnum.INDUSTRY_MORE_THAN_LEVEL);
        }
    }

}
